﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint;

namespace AgileShare.Framework.CamlQueries
{
	/// <summary>
	/// 
	/// </summary>
	public class CrossListQueryBuilder
	{
		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <param name="crossListQueryBuilder">The cross list query builder.</param>
		/// <returns></returns>
		public static CrossListQueryInfo BuildCrossListQueryInfo(CrossListQueryBuilder crossListQueryBuilder)
		{
			CrossListQueryInfo queryInfo = crossListQueryBuilder.BuildCrossListQueryInfo();
			return queryInfo;
		}

		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <param name="lists">The lists.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		/// <returns></returns>
		public static CrossListQueryInfo BuildCrossListQueryInfo(QueryLists lists, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
		{
			return BuildCrossListQueryInfo(new CrossListQueryBuilder(lists, webScope, camlQueryBuilder, useCache, rowLimit));
		}

		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <param name="baseType">Type of the base.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		/// <returns></returns>
		public static CrossListQueryInfo BuildCrossListQueryInfo(ListBaseType baseType, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
		{
			return BuildCrossListQueryInfo(new CrossListQueryBuilder(baseType, webScope, camlQueryBuilder, useCache, rowLimit));
		}

		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <param name="serverTemplate">The server template.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		/// <returns></returns>
		public static CrossListQueryInfo BuildCrossListQueryInfo(int serverTemplate, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
		{
			return BuildCrossListQueryInfo(new CrossListQueryBuilder(serverTemplate, webScope, camlQueryBuilder, useCache, rowLimit));
		}

		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <param name="listIds">The list ids.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		/// <returns></returns>
		public static CrossListQueryInfo BuildCrossListQueryInfo(IEnumerable<Guid> listIds, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
		{
			return BuildCrossListQueryInfo(new CrossListQueryBuilder(listIds, webScope, camlQueryBuilder, useCache, rowLimit));
		}

		/// <summary>
		/// Gets or sets the caml query builder.
		/// </summary>
		/// <value>
		/// The caml query builder.
		/// </value>
		public CamlQueryBuilder CamlQueryBuilder { get; set; }

		/// <summary>
		/// Gets or sets the lists for the query.
		/// </summary>
		/// <value>
		/// The lists.
		/// </value>
		public QueryLists Lists { get; set; }

		/// <summary>
		/// Gets or sets the web scope.
		/// </summary>
		/// <value>
		/// The web scope.
		/// </value>
		public WebScope WebScope { get; set; }

		/// <summary>
		/// Gets or sets the row limit.
		/// </summary>
		/// <value>
		/// The row limit.
		/// </value>
		public uint? RowLimit { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether [use cache].
		/// </summary>
		/// <value>
		///   <c>true</c> if [use cache]; otherwise, <c>false</c>.
		/// </value>
		public bool UseCache { get; set; }

		/// <summary>
		/// Gets or sets the audience field id.
		/// </summary>
		/// <value>
		/// The audience field id.
		/// </value>
		public string AudienceFieldId { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether [filter by audience].
		/// </summary>
		/// <value>
		///   <c>true</c> if [filter by audience]; otherwise, <c>false</c>.
		/// </value>
		public bool FilterByAudience { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether [group by ascending].
		/// </summary>
		/// <value>
		///   <c>true</c> if [group by ascending]; otherwise, <c>false</c>.
		/// </value>
		public bool GroupByAscending { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether [group by audience].
		/// </summary>
		/// <value>
		///   <c>true</c> if [group by audience]; otherwise, <c>false</c>.
		/// </value>
		public bool GroupByAudience { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether [show untargeted items].
		/// </summary>
		/// <value>
		///   <c>true</c> if [show untargeted items]; otherwise, <c>false</c>.
		/// </value>
		public bool ShowUntargetedItems { get; set; }

		/// <summary>
		/// Gets or sets the web URL.
		/// </summary>
		/// <value>
		/// The web URL.
		/// </value>
		public string WebUrl { get; set; }

		/// <summary>
		/// Initializes a new instance of the <see cref="CrossListQuery"/> class.
		/// </summary>
		public CrossListQueryBuilder()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CrossListQuery"/> class.
		/// </summary>
		/// <param name="lists">The lists.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		public CrossListQueryBuilder(QueryLists lists, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
			: this()
		{
			this.Lists = lists;
			this.WebScope = webScope;
			this.CamlQueryBuilder = camlQueryBuilder;
			this.UseCache = useCache;
			this.RowLimit = rowLimit;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CrossListQuery"/> class.
		/// </summary>
		/// <param name="baseType">Type of the base.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		public CrossListQueryBuilder(ListBaseType baseType, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
			: this(new QueryLists { BaseType = baseType }, webScope, camlQueryBuilder, useCache, rowLimit)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CrossListQuery"/> class.
		/// </summary>
		/// <param name="serverTemplate">The server template.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		public CrossListQueryBuilder(int serverTemplate, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
			: this(new QueryLists { ServerTemplate = serverTemplate }, webScope, camlQueryBuilder, useCache, rowLimit)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CrossListQuery"/> class.
		/// </summary>
		/// <param name="listIds">The list ids.</param>
		/// <param name="webScope">The web scope.</param>
		/// <param name="camlQueryBuilder">The caml query builder.</param>
		/// <param name="useCache">if set to <c>true</c> [use cache].</param>
		/// <param name="rowLimit">The row limit.</param>
		public CrossListQueryBuilder(IEnumerable<Guid> listIds, WebScope webScope, CamlQueryBuilder camlQueryBuilder, bool useCache = true, uint? rowLimit = null)
			: this(new QueryLists { ListIds = listIds.ToList() }, webScope, camlQueryBuilder, useCache, rowLimit)
		{
		}

		/// <summary>
		/// Builds the cross list query info.
		/// </summary>
		/// <returns></returns>
		public CrossListQueryInfo BuildCrossListQueryInfo()
		{
			CrossListQueryInfo query = new CrossListQueryInfo();
			query.Lists = this.BuildListsElement().ToString(SaveOptions.DisableFormatting);
			query.Webs = this.BuildWebsElement().ToString(SaveOptions.DisableFormatting);
			string spQuery = this.CamlQueryBuilder.SPQueryStringCondensed;

			// The CrossListQueryInfo.Query property is required. Giving a default value of "Order By Item ID, Ascending" to prevent the query from throwing an exception.
			if (string.IsNullOrEmpty(spQuery))
			{
				spQuery = new CamlQueryBuilder(SPBuiltInFieldId.ID, OrderByDirection.Ascending).SPQueryStringCondensed;
			}

			query.Query = spQuery;

			query.ViewFields = this.CamlQueryBuilder.SPQueryViewFieldsCondensed;

			if (this.RowLimit.HasValue) query.RowLimit = this.RowLimit.Value;
			query.UseCache = this.UseCache;

			if (!string.IsNullOrEmpty(this.AudienceFieldId)) query.AudienceFieldId = this.AudienceFieldId;
			query.FilterByAudience = this.FilterByAudience;
			query.GroupByAscending = this.GroupByAscending;
			query.GroupByAudience = this.GroupByAudience;
			query.ShowUntargetedItems = this.ShowUntargetedItems;
			query.WebUrl = this.WebUrl;

			return query;
		}

		/// <summary>
		/// Builds the cross list query cache.
		/// </summary>
		/// <returns></returns>
		public CrossListQueryCache BuildCrossListQueryCache()
		{
			CrossListQueryInfo query = this.BuildCrossListQueryInfo();
			CrossListQueryCache queryCache = new CrossListQueryCache(query);
			return queryCache;
		}

		private XElement BuildListsElement()
		{
			XElement listsElement = new XElement(CamlConstants.Lists);

			AddAttributeIfNotNull(CamlConstants.BaseType, (int?)this.Lists.BaseType, listsElement);
			AddAttributeIfNotNull(CamlConstants.ServerTemplate, this.Lists.ServerTemplate, listsElement);
			AddAttributeIfNotNull(CamlConstants.Hidden, this.Lists.IncludeHidden, listsElement);
			AddAttributeIfNotNull(CamlConstants.MaxListLimit, this.Lists.MaxListLimit, listsElement);

			foreach (Guid guid in this.Lists.ListIds)
			{
				listsElement.Add(new XElement(CamlConstants.List, new XAttribute(CamlConstants.ID, guid)));
			}

			return listsElement;
		}

		private XElement BuildWebsElement()
		{
			XElement websElement = new XElement(CamlConstants.Webs, new XAttribute(CamlConstants.Scope, this.WebScope));

			return websElement;
		}

		private static void AddAttributeIfNotNull(string attributeName, object value, XElement element)
		{
			if (value != null)
			{
				element.Add(new XAttribute(attributeName, value));
			}
		}
	}
}
